/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.ranch.api.service.impl;

import java.util.Collections;
import java.util.List;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.ranch.api.RanchConstants;
import org.ranch.api.security.util.RanchSecurityUtil;
import org.ranch.api.service.UserService;
import org.ranch.dao.PermissionDAO;
import org.ranch.dao.RoleDAO;
import org.ranch.dao.UserDAO;
import org.ranch.model.Permission;
import org.ranch.model.RecordStatus;
import org.ranch.model.Role;
import org.ranch.model.User;
import org.ranch.model.UserStatus;
import org.ranch.model.exception.RanchOperationFailedException;
import org.ranch.model.exception.RanchValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.googlecode.genericdao.search.Filter;
import com.googlecode.genericdao.search.Search;

/**
 * Implementation of {@link UserService}
 */
@Transactional
@Service("userService")
public class UserServiceImpl implements UserService{
    @Autowired
    private UserDAO userDAO;

    @Autowired
    private PermissionDAO permissionDAO;

    @Autowired
    private RoleDAO roleDAO;

    private Logger log = LoggerFactory.getLogger(this.getClass());

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#findUserByUsername(java.lang
     * .String)
     */
    @Override
    @Transactional(readOnly = true)
    public User findUserByUsername(String username) {
	return userDAO.searchUniqueByPropertyEqual("username", username);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.UserService#getUsers(int)
     */
    @Transactional(readOnly = true)
    public List<User> getUsers(Integer pageNo) {
	Search search = new Search();
	search.setMaxResults(RanchConstants.MAX_NUM_PAGE_RECORD);
	search.addSort("username", false, true);
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);

	/*
	 * if the page number is less than or equal to zero, no need for paging
	 */
	if (pageNo != null && pageNo > 0) {
	    search.setPage(pageNo - 1);
	} else {
	    search.setPage(0);
	}

	List<User> users = userDAO.search(search);
	Collections.sort(users);
	return users;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveUser(User user) throws RanchValidationException {
	// check whether the id is null or empty this is to ensure that the user
	// is new
	if (StringUtils.isBlank(user.getId()) || StringUtils.isEmpty(user.getId())) {
	    // check whether username exists
	    log.debug(this.getClass().getName() + " - checking for existing user");
	    User existingUser = findUserByUsername(user.getUsername());
	    if (existingUser != null) {
		throw new RanchValidationException("a user with this username - "
			+ user.getUsername() + " already exists");
	    }

	    // by default, the user is set to active
	    user.setStatus(UserStatus.ENABLED);
	} else {
	    User existingUser = userDAO.find(user.getId());

	    if (existingUser != null) {
		user.setPassword(existingUser.getPassword());
		user.setSalt(existingUser.getSalt());
		user.setStatus(existingUser.getStatus());
	    }

	    User existingUserWithSimilarUsername = findUserByUsername(user.getUsername());
	    if (existingUserWithSimilarUsername.getId() != user.getId()) {
		throw new RanchValidationException("a user with this username - "
			+ user.getUsername() + " already exists");
	    }

	}

	// check for clear text password and
	// assign the hashedpassword and the salt from the old object
	RanchSecurityUtil.prepUserCredentials(user);

	userDAO.save(user);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.UserService#getUsers()
     */
    @Override
    @Transactional(readOnly = true)
    public List<User> getUsers() {
	List<User> users = userDAO.searchByRecordStatus(RecordStatus.ACTIVE);
	Collections.sort(users);
	return users;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#deleteUser(org.mak.cis.mohr.
     * model.User)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void deleteUser(User user) throws RanchOperationFailedException {
	if (StringUtils.equalsIgnoreCase(user.getUsername(), "administrator")) {
	    throw new RanchOperationFailedException("cannot delete default administrator");
	}

	userDAO.delete(user);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#saveRole(org.mak.cis.mohr.model
     * .Role)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveRole(Role role) throws RanchValidationException {
	if (StringUtils.isBlank(role.getName()) || StringUtils.isEmpty(role.getName())) {
	    throw new RanchValidationException("the name of the role is not supplied");
	}

	if (StringUtils.isBlank(role.getDescription())
		|| StringUtils.isEmpty(role.getDescription())) {
	    throw new RanchValidationException("the description of the role is not supplied");
	}

	if (StringUtils.isBlank(role.getId()) || StringUtils.isEmpty(role.getId())) {
	    Role existingRole = roleDAO.searchUniqueByPropertyEqual("name", role.getName());
	    if (existingRole != null) {
		throw new RanchValidationException("a role is the given name already exists");
	    }
	}

	roleDAO.save(role);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.UserService#getRoles()
     */
    @Override
    @Transactional(readOnly = true)
    public List<Role> getRoles() {
	List<Role> roles = roleDAO.searchByRecordStatus(RecordStatus.ACTIVE);
	Collections.sort(roles);
	return roles;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#getRolesByPage(java.lang.Integer
     * )
     */
    @Override
    @Transactional(readOnly = true)
    public List<Role> getRolesByPage(Integer pageNo) {
	Search search = new Search();
	search.setMaxResults(RanchConstants.MAX_NUM_PAGE_RECORD);
	search.addSort("id", false, true);
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);

	/*
	 * if the page number is less than or equal to zero, no need for paging
	 */
	if (pageNo > 0) {
	    search.setPage(pageNo - 1);
	} else {
	    search.setPage(0);
	}

	List<Role> roles = roleDAO.search(search);
	Collections.sort(roles);
	return roles;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#deleteRole(org.mak.cis.mohr.
     * model.Role)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void deleteRole(Role role) throws RanchOperationFailedException {
	if (StringUtils.equalsIgnoreCase(role.getName(), Role.DEFAULT_ADMIN_ROLE)) {
	    throw new RanchOperationFailedException("cannot delete default administration role");
	}

	roleDAO.remove(role);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.UserService#getPermissions()
     */
    @Override
    public List<Permission> getPermissions() {
	List<Permission> permissions = permissionDAO.searchByRecordStatus(RecordStatus.ACTIVE);
	Collections.sort(permissions);
	return permissions;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#findUsersByUsername(java.lang
     * .String)
     */
    @Override
    @Transactional(readOnly = true)
    public List<User> findUsersByUsername(String username) {
	Search search = new Search();
	search.addFilter(new Filter("username", username, Filter.OP_LIKE));
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	List<User> users = userDAO.search(search);

	Collections.sort(users);
	return users;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#getUserById(java.lang.String)
     */
    @Override
    @Transactional(readOnly = true)
    public User getUserById(String userId) {
	return userDAO.searchUniqueByPropertyEqual("id", userId);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#getRoleById(java.lang.String)
     */
    @Override
    @Transactional(readOnly = true)
    public Role getRoleById(String id) {
	Role role = roleDAO.searchUniqueByPropertyEqual("id", id);
	Collections.sort(role.getPermissions());
	return role;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#findRolesByName(java.lang.String
     * )
     */
    @Override
    @Transactional(readOnly = true)
    public List<Role> findRolesByName(String searchString) {
	Search search = new Search();
	search.addFilter(new Filter("name", searchString, Filter.OP_LIKE));
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	List<Role> roles = roleDAO.search(search);
	Collections.sort(roles);
	return roles;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#getPermissionById(java.lang.
     * String)
     */
    @Override
    public Permission getPermissionById(String id) {
	return permissionDAO.searchUniqueByPropertyEqual("id", id);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.UserService#savePermision(org.mak.cis.mohr
     * .model.Permission)
     */
    @Override
    public void savePermision(Permission permission) throws RanchValidationException {
	if (StringUtils.isBlank(permission.getName()) || StringUtils.isEmpty(permission.getName())) {
	    throw new RanchValidationException("the name of the permission is not supplied");
	}

	if (StringUtils.isBlank(permission.getDescription())
		|| StringUtils.isEmpty(permission.getDescription())) {
	    throw new RanchValidationException("the description of the permission is not supplied");
	}

	if (StringUtils.isBlank(permission.getId()) || StringUtils.isEmpty(permission.getId())) {
	    Permission existingPerm = permissionDAO.searchUniqueByPropertyEqual("name",
		    permission.getName());
	    if (existingPerm != null) {
		throw new RanchValidationException("a permission with the given name already exists");
	    }
	}

	permissionDAO.save(permission);

    }

    @Override
    public int getTotalNumberOfUsers() {
	Search search = new Search();
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	return userDAO.count(search);
    }

    /**
     * @return the userDAO
     */
    public UserDAO getUserDAO() {
	return userDAO;
    }

    /**
     * @param userDAO
     *            the userDAO to set
     */
    public void setUserDAO(UserDAO userDAO) {
	this.userDAO = userDAO;
    }

    /**
     * Search for users
     * 
     * @see org.mak.cis.mohr.api.service.UserService#searchUserByLoginName(java.lang.String)
     */
    @Override
    @Transactional(readOnly = true)
    public List<User> searchUserByLoginName(String query) {
	Search search = prepareUserSearchByLoginName(query);
	List<User> users = userDAO.search(search);
	Collections.sort(users);
	return users;
    }

    /**
     * Prepares the search to be used in searching users
     * 
     * @param query
     * @return
     */
    public static Search prepareUserSearchByLoginName(String query) {

	Search search = new Search();
	String param = new StringBuilder().append("%").append(StringEscapeUtils.escapeSql(query))
		.append("%").toString();
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	search.addFilterLike("username", param);
	search.addSort("username", false, true);

	return search;
    }

    @Override
    public int getNumberOfUsersInSearch(String query) {
	Search search = prepareUserSearchByLoginName(query);
	return userDAO.count(search);
    }

    @Override
    public List<User> searchUserByLoginName(String query, Integer pageNo) {
	Search search = prepareUserSearchByLoginName(query);
	search.setMaxResults(RanchConstants.MAX_NUM_PAGE_RECORD);
	
	if (pageNo != null && pageNo > 0) {
	    search.setPage(pageNo - 1);
	} else {
	    search.setPage(0);
	}
	
	List<User> users = userDAO.search(search);
	Collections.sort(users);
	return users;
    }
}
